#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include "hashtable.h"
#include "person.h"
#include "dynarray.h"
#include "date.h"
#include "familytree.h"
#include "jsonparser.h"
#include "fileio.h"

/**
 * Hakee json -tiedostosta luetun personia vastaavan hashtable -rakenteen
 * @param persons dynarray joka sisältää kaikki personeita vastaavat hashtablet
 * @param i haettavan personin indeksi
 * @param result muutuja johon merkitään virhekoodit
 * @return  haettu hashtable, virhetilanteessa NULL
 */
hashtable* get_person_ht(dynarray* persons, unsigned int i, int* result) { //Ossi
    json_item* person_item = (json_item*) da_get(persons, i);
    if (person_item == NULL) {
        *result = *result | STATE_PARTIAL;
        return NULL;
    }
    if (person_item->type != TYPE_HASHTABLE) {
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return NULL;
    }

    return (hashtable*) person_item->data;
}

/**
 * Hakee personia vastaavasta hashtablesta personin nimen
 * @param person_ht personia vastaava hashtable
 * @param result muuttuja johon merkitään virhekoodit
 * @return nimi, virhetilanteessa NULL, jos annetun niminen henkilö 
 *         oli jo olemassa, annetaan viittaus olemassaolevaan nimeen
 */
wchar_t* get_person_name(hashtable* person_ht, int* result) { //Ossi
    json_item* per_name_item = ht_get(person_ht, L"name");
    if (per_name_item == NULL) { // ei nimeä
        *result = *result | STATE_PARTIAL;
        return NULL;
    }
    if (per_name_item->type != TYPE_STRING) { // väärän tyyppistä dataa
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return NULL;
    }

    wchar_t* per_name_ht = (wchar_t*) per_name_item->data;
    if (per_name_ht == NULL) { //jos nimeä ei löydy, siirrytään seuraavaan henkilöön
        *result = *result | STATE_INVALID_DATA;
        return NULL;
    }

    person* p = ft_get_person_by_name(per_name_ht);

    wchar_t* per_name;
    if (p != NULL) { //jos nimetty henkilö on olemassa, palautetaan jo varattu nimi
        per_name = p->name;
    } else {
        int len = wcslen(per_name_ht);
        per_name = malloc(sizeof(wchar_t)*(len+1));
        if (per_name == NULL) {
            *result = *result | STATE_UNKNOWN;
            return NULL;
        }

        wcscpy(per_name, per_name_ht); //Kopioidaan nimi jotta voidaan vapauttaa hashtable lopuksi
    }
    return per_name;
}

/**
 * Hakee henkilön sukupuolen hashtablesta
 * @param person henkilöä vastaa hashtable
 * @param result virhekoodimuuttuja
 * @return henkilön sukupuoli, virhetilanteessa L'\0'
 */
wchar_t get_person_gender(hashtable* person, int* result) { //Ossi
    json_item* gender_item = ht_get(person, L"gender");
    if (gender_item == NULL) {
        *result = *result | STATE_PARTIAL;
        return L'\0';
    }
    if (gender_item->type != TYPE_STRING) {
        *result = *result | STATE_PARTIAL;
        return L'\0';
    }

    return ((wchar_t*) gender_item->data)[0];
}

/**
 * Hakee henkilön syntymäpäivän
 * @param person henkilön tiedot sisältävä hashtable
 * @param result virhekoodimuuttuja
 * @return henkilön syntymäpäivä, virhetilanteessa NULL_DATE
 */
date get_person_birthday(hashtable* person, int* result) { //Ossi
    json_item* date_item = ht_get(person, L"birthdate");

    if (date_item == NULL) { // ei päivämäärää tallennettu
        *result = *result | STATE_PARTIAL;
        return NULL_DATE;
    }
    if (date_item->type != TYPE_STRING) { //väärän tyyppistä dataa
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return NULL_DATE;
    }

    date bd = date_from_string((wchar_t*) (date_item->data));

    if (!date_compare(&bd, &NULL_DATE)) { // päivämäärä ei ole validi
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return NULL_DATE;
    }

    return bd;
}

/**
 * Hakee henkilön kuolinpäivän
 * @param person henkilön tiedot sisältävä hashtable
 * @param result virhekoodimuuttuja
 * @return henkilön kuolinpäivä, virhetilanteessa NULL_DATE
 */
date person_get_deathdate(hashtable* person, int* result) { //Ossi
    json_item* date_item = ht_get(person, L"deathdate");

    date dd;

    if (date_item == NULL) {
        dd = NULL_DATE; //jos kuolinpäivää ei ole tallennettu, oletetaan null date     
    } else if (date_item->type != TYPE_STRING) { //Väärää dataa tiedostossa
        *result = *result | STATE_INVALID_DATA;
        dd = NULL_DATE;
    } else {
        dd = date_from_string((wchar_t*) date_item->data);
    }
    return dd;
}

/**
 * Hakee wchar_t tyyppisen merkkijonon relaatiota vastaavasta hashtablesta
 * @param key haettavaa merkkijonoa vastaava avain
 * @param rel relaation tiedot sisältävä hashtable
 * @param result virhekoodimuuttuja
 * @return haettu merkkijono,
 */
wchar_t* rel_get_str(wchar_t* key, hashtable* rel, int* result) { //Ossi
    json_item* _item = ht_get(rel, key);

    if (_item == NULL) { // ei päivämäärää tallennettu
        *result = *result | STATE_PARTIAL;
        return NULL;
    }
    if (_item->type != TYPE_STRING) { //väärän tyyppistä dataa
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return NULL;
    }

    return (wchar_t*) _item->data;
}

/**
 * Hakee relaation tyypin
 * @param rel relaation tiedot sisältävä hashtable
 * @param result virhekoodimuuttuja
 * @return relaation tyyppi, REL_INVALID_VALUE virhetilanteessa
 */
rel_t rel_get_type(hashtable* rel, int* result) { //Ossi 
    json_item* _item = ht_get(rel, L"type");

    if (_item == NULL) { // ei päivämäärää tallennettu
        *result = *result | STATE_PARTIAL;
        return REL_INVALID_VALUE;
    }
    if (_item->type != TYPE_INT) { //väärän tyyppistä dataa
        *result = *result | STATE_PARTIAL;
        *result = *result | STATE_INVALID_DATA;
        return REL_INVALID_VALUE;
    }

    return int_to_relt(*((int*) _item->data));
}

/**
 * Lisää person hashtablesta löydetyt relaatiot sukupuuhun
 * @param person_ht hashtable, joka sisältää personin tiedot
 * @param result virhekoodimuuttuja
 */
void load_relations(hashtable* person_ht, int* result) { //Ossi
    json_item* relations_item = ht_get(person_ht, L"relations");
    if (relations_item == NULL) { // ei tallennettuja suhteita
        return;
    }
    if (relations_item->type != TYPE_ARRAY) { // väärää dataa
        *result = *result | STATE_INVALID_DATA;
        return;
    }

    dynarray* relations = (dynarray*) relations_item->data;

    for (unsigned int j = 0; j < relations->top; ++j) { //luetaan ja lisätään suhteet henkilölle
        json_item* rel_item =(json_item*) da_get(relations, j);
        if (rel_item == NULL){
            *result = *result | STATE_PARTIAL | STATE_UNKNOWN;
            continue;
        }
        if (rel_item->type != TYPE_HASHTABLE){
            *result = *result | STATE_PARTIAL | STATE_INVALID_DATA;
            continue;
        }
        
        
        
        hashtable* relation_ht = (hashtable*)rel_item->data; 
        wchar_t* person1, *person2;
        rel_t type;

        person1 = rel_get_str(L"person1", relation_ht, result);
        if (person1 == NULL) continue;

        person2 = rel_get_str(L"person2", relation_ht, result);
        if (person2 == NULL) continue;

        type = rel_get_type(relation_ht, result);
        if (type == REL_INVALID_VALUE) continue;

        person* p1 = ft_get_person_by_name(person1);

        person* p2 = ft_get_person_by_name(person2);
        
        
        
        if (p1 == NULL) { //jos henkilöä ei vielä löytynyt, lisätään "potentiaalinen" henkilö
            //Tiedot korjataan, kun henkilö löydetään tiedostosta
            wchar_t *name = malloc(sizeof(wchar_t)*(wcslen(person1) +1));
            if (name == NULL){
                *result = *result | STATE_PARTIAL | STATE_UNKNOWN;
                continue;
            }
            wcscpy(name,person1);// kopioidaan nimi jotta hashtable voidaan vapauttaa
            int pi = ft_add_person(name, NULL_DATE, NULL_DATE, L'x');
            p1 = (person*) da_get(FT, pi);
        }

        if (p2 == NULL) {
            wchar_t *name = malloc(sizeof(wchar_t)*(wcslen(person2) +1));
            if (name == NULL){
                *result = *result | STATE_PARTIAL | STATE_UNKNOWN;
                continue;
            }
            wcscpy(name,person2); // kopioidaan nimi jotta hashtable voidaan vapauttaa
            int pi = ft_add_person(name, NULL_DATE, NULL_DATE, L'x');
            p2 = (person*) da_get(FT, pi);
        }
        
        per_add_relation(p1, p2, type);

    }
}

/**
 * Lataa sukupuun tiedostosta
 * @param filename ladattavan tiedoston nimi
 */
int load_familytree(char* filename) { //Ossi
    int result = 0x0;

    json_parser* p = json_init(filename);

    if (p == NULL) return STATE_UNKNOWN;

    hashtable* h = json_next_hashtbl(p);
    
    json_dispose(p);

    if (h == NULL) return result;

    json_item* ft_json = ht_get(h, L"FamilyTree");

    if (ft_json == NULL) return result;
    if (ft_json->type != TYPE_ARRAY) {
        return STATE_INVALID_DATA;
    }
    dynarray* persons_array = (dynarray*) ft_json->data;
    
   

    for (unsigned int i = 0; i < persons_array->top; ++i) { //luetaan henkilöt



        hashtable* person_ht = get_person_ht(persons_array, i, &result);
        if (person_ht == NULL) continue;

        wchar_t* per_name = get_person_name(person_ht, &result);
        if (per_name == NULL) continue;

        wchar_t gender = get_person_gender(person_ht, &result);
        if (gender == L'\0') {
            free(per_name);
            continue;
        }

        date bd = get_person_birthday(person_ht, &result);
        if (!date_compare(&bd, &NULL_DATE)) {
            free(per_name);
            continue;
        }

        date dd = person_get_deathdate(person_ht, &result);

        person* p = ft_get_person_by_name(per_name);

        if (p != NULL) { //jos henkilön nimi löytyi aiemmin relaatiosta, korjataan tiedot.
            p->gender = gender;
            p->birthdate = bd;
            p->deathdate = dd;
        } else {
            ft_add_person(per_name, bd, dd, gender); //lisätään henkilö familytreehen
        }

        load_relations(person_ht, &result);

    }
    ht_free(h);
    
    return result;
}

/*
 *	Tallentaa FamilyTreen sisällön annettuun tiedostoon
 */
void save_familytree(char* filename) { //Joni
    FILE* out = fopen(filename, "w");
    if (out != NULL) {
        fprintf(out, "{ \"FamilyTree\": [");
        for (unsigned int i = 0; i < FT->top; ++i) {
            person * current_person = (person*) da_get(FT, i);
            if (i > 0) fprintf(out, ",\n");
            fprintf(out, "{ \"name\": \"%ls\", \"gender\": \"%lc\","
                    , current_person->name, current_person->gender);
            date bd = current_person->birthdate;
            date dd = current_person->deathdate;

            fprintf(out, "\"birthdate\": \"%d-%d-%d\", \"deathdate\":\"%d-%d-%d\"", bd.day, bd.month, bd.year, dd.day, dd.month, dd.year);
            if (current_person->relations->top > 0) {
                fprintf(out, ", \"relations\": [\n");
                for (unsigned int i = 0; i < current_person->relations->top; ++i) {
                    relation * cur_rel = da_get(current_person->relations, i);
                    if (i > 0) fprintf(out, ",");
                    fprintf(out, "{ \"person1\": \"%ls\", \"person2\": \"%ls\", \"type\": %d }\n",
                            cur_rel->person1->name, cur_rel->person2->name, relt_to_int(cur_rel->type));
                }
                fprintf(out, "]");
            }
            fprintf(out, "}");
        }
        fprintf(out, "] }");
    }
    fclose(out);
}
